package mysgoog.board;

import java.util.*;

import mysgoog.bot.algo.Evaluator;
import mysgoog.gtp.*;
import mysgoog.util.*;

public class Dragon extends HashSet<Worm> {
	private static final long serialVersionUID = 1L;
	private final Board board;
	public final Color color;
	static final private double[] w = { 1, 0.5, 0.35, 0.25 };
	static final private double wu = 1;
	static final private double wt = 0.2;
	static final private double we = 1.6;
	private Evaluator eva;

	public Dragon(Board board, Color color) {
		this.board = board;
		this.color = color;
		eva = new Evaluator((DragonBoard) board);
	}

	private PosSet stones = new PosSet();
	private PosSet libs = new PosSet();
	private PosSet eyes = new PosSet();
	private PosSet halfEyes = new PosSet();
	private PosSet territories = new PosSet();
	// private LinkedList<PosSet> linkEyes = new LinkedList<PosSet>();
	private int tsn = -1;
	private double pon = -1;// Possible Omission Number

	public boolean isAlive() {
		update();
		return eyes.size() >= 2;
	}

	public boolean isDead() {
		update();
		// TODO
		if (eyes.size() > 1)
			return false;
		if (pon < -1.1 && eyes.size() + halfEyes.size() < 3)
			return true;
		else
			return false;
	}

	public boolean isDangerous() {
		update();
		for (Worm worm : this)
			if (worm.getLibs().size() <= 1)
				return true;
		// depend on the size of the dragon and the value of pon
		if (eyes.size() > 1)
			return false;
		if (pon < 1)
			return true;
		return false;
	}

	public PosSet getStones() {
		update();
		return stones;
	}

	public PosSet getLibs() {
		update();
		return libs;
	}

	public PosSet getEyes() {
		update();
		return eyes;
	}

	/*
	 * return both half eyes and flase eyes
	 */
	public PosSet getHalfEyes() {
		update();
		return halfEyes;
	}

	public PosSet getTerritories() {
		update();
		return territories;
	}

	public double getPON() {
		update();
		return pon;
	}

	private int countEyeCorner(int pos) {
		return stones.containCount(board.getDiag(pos))
				+ eyes.containCount(board.getDiag(pos));
	}

	private int trueEyeThres(int pos) {
		int thres;
		if (board.isCorner(pos))
			thres = 1;
		else if (board.isEdge(pos))
			thres = 2;
		else
			thres = 3;
		return thres;
	}

	private void update() {
		if (tsn < board.getTSN()) {
			tsn = board.getTSN();

			// update stones;
			stones.clear();
			for (Worm worm : this)
				for (int pos : worm)
					stones.add(pos);

			// update libs
			libs.clear();
			for (Worm worm : this)
				libs.addAll(worm.getLibs());

			// update eyes & halfEyes
			eyes.clear();
			halfEyes.clear();
			for (int pos : libs)
				if (stones.containsAll(board.getAdjacent(pos))) {
					int thres = trueEyeThres(pos);
					if (stones.containCount(board.getDiag(pos)) >= thres)
						eyes.add(pos);
					else
						halfEyes.add(pos);
				}

			while (true) {
				ArrayList<Integer> al = new ArrayList<Integer>();
				for (int pos : halfEyes)
					if (countEyeCorner(pos) >= trueEyeThres(pos)) {
						al.add(pos);
					}
				if (al.size() > 0) {
					halfEyes.removeAll(al);
					eyes.addAll(al);
				} else
					break;
			}

			// update linkEyes
			/*
			 * linkEyes.clear(); for (int pos : libs) { PosSet p = new PosSet();
			 * 
			 * }
			 */
			// update territories
			territories.clear();
			territories.addAll(stones);
			territories.addAll(eyes);
			Color othercolor = color.complement();
			for (int pos : libs) {
				ArrayList<Integer> neis = board.getAdjacent(pos);

				boolean danger = false;
				for (int k = 0; k < neis.size(); k++) {
					int apos = neis.get(k);
					if (board.getStone(apos) == othercolor) {
						danger = true;
						break;
					}
				}

				if (!danger) {

					territories.add(pos);

					for (int k = 0; k < neis.size(); k++) {
						int apos = neis.get(k);
						if (board.getStone(apos) == Color.EMPTY
								&& !libs.contains(apos)
								&& ((DragonBoard) board).territoryAt(apos) == null) {
							double inf = eva.calcInfuluence(apos);
							if (inf > 2 && color == Color.WHITE || inf < -2
									&& color == Color.BLACK)
								territories.add(apos);
						}
					}
				}
			}

			// TODO territories
			// update Possible Omission Number
			pon = calcPON();
		}
	}

	private int getKosuri(int pos) {

		int kosuri = 0;
		Color otherColor = color.complement();
		int i = board.posX(pos);
		int j = board.posY(pos);
		ArrayList<Integer> neighbors = board.getAdjacent(pos);

		for (int k = 0; k < neighbors.size(); k++) {
			int apos = neighbors.get(k);
			if (board.getStone(apos) == otherColor)
				kosuri += 1;
		}
		if (board.isEdge(pos))
			kosuri++;
		if (kosuri > 0
				&& (i == 1 || j == 1 || i == board.BOARD_LENGTH - 2 || j == board.BOARD_LENGTH - 2))
			kosuri++;
		return kosuri;
	}

	/**
	 * Estimating the Possible Omission Number for Groups in Go by the Number of
	 * n-th Dame
	 * 
	 * @return
	 */
	private double calcPON() {

		update();
		double S = 0;
		/**
		 * from position to the dame value
		 */
		LinkedList<PosSet> ll = new LinkedList<PosSet>();
		for (int i = 0; i < 4; i++)
			ll.addLast(new PosSet());
		// Hashtable<Integer, Integer> dames = new Hashtable<Integer,
		// Integer>();
		Hashtable<Integer, Boolean> ks = new Hashtable<Integer, Boolean>();
		// LinkedList<Integer> list = new LinkedList<Integer>();
		Hashtable<Integer, Integer> pre = new Hashtable<Integer, Integer>();
		int kosuri;
		ll.get(0).addAll(libs);

		/*
		 * for (Integer pos : stones) { // color = stmp.getState();
		 * ArrayList<Integer> neighbors = board.getAdjacent(pos); for (int k =
		 * 0; k < neighbors.size(); ++k) { int apos = neighbors.get(k); if
		 * (!board.getStone(apos).isFilled()) { ll.get(0).add(apos); //
		 * dames.put(apos, 1); // list.addLast(apos); } } }
		 */

		for (int i = 0; i < 4; i++) {
			// distance of i
			for (Integer stmp : ll.get(i)) {
				// calculate the current S

				kosuri = getKosuri(stmp);

				if (pre.get(stmp) != null) {
					int pretmp = pre.get(stmp);
					if (ks.get(pretmp) != null && ks.get(pretmp) == true)
						kosuri++;
				}
				if (kosuri > 1)
					continue;
				if (board.isEdge(stmp)) {
					S += w[i] * we;
					if (kosuri >= 1)
						ks.put(stmp, true);
					else
						ks.put(stmp, false);
				} else if (kosuri == 1) {
					S += w[i] * wt;
					ks.put(stmp, true);
				} else {
					S += w[i] * wu;
					ks.put(stmp, false);
				}
				// consider its adjacent
				if (i < 3) {
					ArrayList<Integer> neighbors = board.getAdjacent(stmp);
					for (int k = 0; k < neighbors.size(); ++k) {
						int apos = neighbors.get(k);
						boolean calculated = false;
						if (!board.getStone(apos).isFilled()) {
							switch (i) {
							case 0:
								break;
							case 1:
								if (ll.get(0).contains(apos)
										|| ll.get(1).contains(apos))
									calculated = true;
								break;
							case 2:
								if (ll.get(0).contains(apos)
										|| ll.get(1).contains(apos)
										|| ll.get(2).contains(apos))
									calculated = true;
								break;
							default:
								Lib.assertNotReached();
							}
							if (calculated)
								continue;
							ll.get(i + 1).add(apos);
							pre.put(apos, stmp);
						}
					}
				}
			}
		}

		return S * 0.33 - 1.96;
	}
}
